import fs from 'fs';
import path from 'path';
import { Application } from '../Application';
import { logger } from '../util/logger';
import { miscCfg, RpcCallRes, RpcMsg, RpcMsgId, SvrInfo } from '../util/defines';
import { RpcClient } from './RpcClinet';
import { RpcHandler } from '../handler/RpcHandler';
import { RpcServer } from './RpcServer';
import { RpcSocket } from './RpcSocket';

/**
 * Rpc服务
 */
export class RpcService {

    app: Application;
    svr: RpcServer;
    svrs: { [sid: string]: RpcSocket } = {};
    rpcCallers: { [id: number]: RpcCaller } = {};
    private _rpc: Rpc;
    private _rpcHandlers: { [handlerName: string]: any };
    private _waitConnCbs: { [sid: string]: ((err?: string, socket?: RpcSocket) => void)[] } = {};
    private _autoWatchers: { [stype: string]: fs.FSWatcher } = {};
    private _reloadTimers: { [filePath: string]: NodeJS.Timeout } = {};

    constructor(app: Application, onStart: () => void) {
        app.rpc = (sid, notify) => { RpcCaller.toSid = sid; RpcCaller.notify = notify; return this._rpc; };
        this.app = app;
        this.loadRpcHandlers();
        this.svr = new RpcServer(app, onStart);
    }

    /**
     * 加载RpcHandlers
     */
    loadRpcHandlers() {
        this._rpc = {} as any;
        this._rpcHandlers = {};
        let serversPath = path.resolve(this.app.base, miscCfg.serversPath);
        if (!fs.existsSync(serversPath)) return;

        //遍历服务器目录
        fs.readdirSync(serversPath).forEach(stype => {
            if (!this.app.svrCfg[stype]) return;
            let rpcPath = path.resolve(serversPath, stype, "rpc");
            if (!fs.existsSync(rpcPath) || !fs.statSync(rpcPath).isDirectory()) return;

            //遍历rpc目录
            this._rpc[stype] = {};
            fs.readdirSync(rpcPath).forEach(fileName => {
                let filePath = path.resolve(rpcPath, fileName);
                this.loadRpcHandler(stype, filePath, fileName);
            })
            miscCfg.autoReloadHandler && this.autoReloadRpcHandler(stype, rpcPath);
        });
    }

    /**
     * 加载单个RpcHandler
     * @param filePath 
     * @param fileName 
     */
    loadRpcHandler(stype: string, filePath: string, fileName: string) {
        if (!fileName.endsWith(".js") && !fileName.endsWith(".ts")) return false;
        try {
            let cls: any = require(filePath).default;
            delete require.cache[filePath];
            if (!cls || typeof cls !== "function") return false;

            //构建handler
            let handlerName = fileName.slice(0, fileName.length - 3);
            if (stype === this.app.stype) {
                let old = this._rpcHandlers[handlerName];
                let hanlder = new (cls as typeof RpcHandler)(this.app);
                old && hanlder.onReload(old);
                this._rpcHandlers[handlerName] = hanlder;
            }
            //遍历handler方法
            this._rpc[stype][handlerName] = {};
            let prototype = cls.prototype;
            let keys = Object.getOwnPropertyNames(prototype);
            for (let funcName of keys) {
                if (funcName === "constructor" || typeof prototype[funcName] !== "function") continue;
                this._rpc[stype][handlerName][funcName] = (...args: any[]) => new Promise(resolve => RpcCaller.call(this.app, stype, handlerName, funcName, args, resolve));
            }
            return true;
        } catch (err: any) {
            if (stype == this.app.stype) {
                logger.error(`加载RpcHandler<${fileName}>失败` + err.statck, this, this.loadRpcHandler);
            }
        }
        return false;
    }

    /**
     * 重载
     * @param stype 
     * @param handlerName 
     * @returns 
     */
    reloadRpcHandlers(stype: string, handlerName?: string) {
        if (stype == "*") {
            //重载所有
            logger.info(`重载所有RpcHandler`, this, this.reloadRpcHandlers);
            let serversPath = path.resolve(this.app.base, miscCfg.serversPath);
            if (!fs.existsSync(serversPath)) return;

            //遍历服务器目录
            fs.readdirSync(serversPath).forEach(stype => {
                if (!this.app.svrCfg[stype]) return;
                let rpcPath = path.resolve(serversPath, stype, "rpc");
                if (!fs.existsSync(rpcPath) || !fs.statSync(rpcPath).isDirectory()) return;

                //遍历rpc目录
                fs.readdirSync(rpcPath).forEach(fileName => {
                    let filePath = path.resolve(rpcPath, fileName);
                    this.loadRpcHandler(stype, filePath, fileName);
                })
            });
        } else {
            if (!this.app.svrCfg[stype]) return;
            let rpcPath = path.resolve(this.app.base, miscCfg.serversPath, stype, "rpc");
            if (!fs.existsSync(rpcPath) || !fs.statSync(rpcPath).isDirectory()) return;

            if (handlerName && handlerName != "*") {
                //重载单个
                let format = this.app.isTs ? ".ts" : ".js";
                let filePath = path.resolve(rpcPath, handlerName + format);
                let fileName = handlerName + format;
                if (!fs.existsSync(filePath + format)) {
                    logger.error(`重载RpcHandler<${handlerName}>失败,文件不存在`, this, this.reloadRpcHandlers);
                    return;
                }
                if (this.loadRpcHandler(stype, filePath, fileName)) {
                    logger.info(`已重载RpcHandler<${handlerName}>`, this, this.reloadRpcHandlers);
                }
            } else {
                //重载目录
                //遍历rpc目录
                logger.info(`重载${stype}目录下所有RpcHandler`, this, this.reloadRpcHandlers);
                fs.readdirSync(rpcPath).forEach(fileName => {
                    let filePath = path.resolve(rpcPath, fileName);
                    this.loadRpcHandler(stype, filePath, fileName);
                });
            }
        }
    }

    /**
     * 监听目录文件变化自动重载消息Handler
     * @param stype
     * @param rpcPath 
     */
    private autoReloadRpcHandler(stype: string, rpcPath: string) {
        if (this._autoWatchers[rpcPath]) return;
        this._autoWatchers[rpcPath] = fs.watch(rpcPath, (event, fileName) => {
            let filePath = path.resolve(rpcPath, fileName);
            if (this._reloadTimers[filePath]) {
                clearTimeout(this._reloadTimers[filePath]);
                delete this._reloadTimers[filePath];
            }

            if (event === "change" || (event === "rename" && fs.existsSync(filePath))) {
                this._reloadTimers[filePath] = setTimeout(() => {
                    delete this._reloadTimers[filePath];
                    if (this.loadRpcHandler(stype, filePath, fileName)) {
                        event === "change" ?
                            logger.info(`已重载RpcHandler<${fileName}>`, this, this.autoReloadRpcHandler) :
                            logger.info(`已加载MsgHandler<${fileName}>`, this, this.autoReloadRpcHandler);
                    }
                }, 500);
            }
        });
    }

    /**
     * 与某个服务器建立Rpc连接
     * @param svr 
     */
    connect(svr: SvrInfo) {
        if (!svr) return;
        if (this.svrs[svr.id]) return; //已建立连接了
        new RpcClient(this.app, svr);
    }

    /**
     * 断开与某个服务器的Rpc连接
     * @param svr 
     */
    disconnect(svr: SvrInfo) {
        if (!svr) return;
        let socket = this.svrs[svr.id];
        if (!socket) return;
        socket.close();
    }

    /**
     * 向某个服务器发送Rpc消息
     * @param sid 
     * @param msgId 
     * @param msg 
     * @returns 
     */
    async send<MsgId extends RpcMsgId>(sid: string, msgId: MsgId, msg: RpcMsg<MsgId>) {
        return new Promise<string>(res => {
            let socket = this.svrs[sid];
            if (!socket) {
                let svr = this.app.svrCfgIdMap[sid];
                if (!svr) {
                    let err = `消息发送失败,非法的服务器${sid}`;
                    logger.error(err, this, this.send);
                    res(err);
                    return;
                }
                let cb = (err: string, socket: RpcSocket) => {
                    if (err) {
                        logger.error(`消息发送失败,未能连接上服务器${sid}`, this, this.send);
                        res(err);
                        return;
                    }
                    socket.send(msgId, msg);
                    res(null);
                };
                if (this._waitConnCbs[sid]) {
                    this._waitConnCbs[sid].push(cb);
                } else {
                    this._waitConnCbs[sid] = [cb];
                    this.connect(svr);
                }
                return;
            }
            socket.send(msgId, msg);
            res(null);
        })
    }

    /**
     * 执行等待rpc连接的回调
     * @param sid 
     * @param err 
     * @param socket 
     */
    runWaitConnCbs(sid: string, err?: string, socket?: RpcSocket) {
        let cbs = this._waitConnCbs[sid];
        if (cbs) {
            for (let cb of cbs) {
                cb(err, socket);
            }
            delete this._waitConnCbs[sid];
        }
    }

    /**
     * 调用Rpc方法
     */
    async callRpcFunc(handlerName: string, funcName: string, args: any[]): Promise<RpcCallRes> {
        try {
            let handler = this._rpcHandlers[handlerName];
            let res = args ? handler[funcName](...args) : handler[funcName]();
            return res instanceof Promise ? await res : res;
        } catch (err) {
            return { err: { code: -1, msg: (err as Error).message } };
        }
    }

    /**
     * Rpc调用结果返回
     * @param callerId 
     * @param res 
     * @returns 
     */
    onRpcCallRes(callerId: number, res: RpcCallRes) {
        let caller = this.rpcCallers[callerId];
        if (!caller) return;
        caller.onCallRes(res);
    }

}

/**
 * Rpc方法调用者
 */
class RpcCaller {

    static toSid: string;
    static notify: boolean;

    private static _pool: RpcCaller[] = [];
    private static _id = 1;
    private static getId() {
        let id = this._id++;
        if (this._id > 268435455) this._id = 1;
        return id;
    }

    app: Application;
    id: number;
    toSid: string;
    hanlder: string;
    func: string;
    private _resolve: (val: any) => void;
    private _callTimeoutTimer: NodeJS.Timer;

    private constructor() { }

    static async call(app: Application, stype: string, handler: string, func: string, args: any[], resolve: (val: any) => void) {
        let caller = this._pool.pop() || new RpcCaller();
        let id = this.getId();
        caller.app = app;
        caller.id = id;
        caller.toSid = this.toSid;
        caller.hanlder = handler;
        caller.func = func;
        caller._resolve = resolve;
        caller._callTimeoutTimer = setTimeout(caller.onCallTimeout.bind(caller), miscCfg.rpcCallTimeout);
        app.rpcService.rpcCallers[id] = caller;

        if (RpcCaller.toSid === "*") {
            //广播给所有该类型的服务器
            let svrs = app.svrs[stype];
            if (svrs) {
                for (let svr of svrs) {
                    app.rpcService.send(svr.id, RpcMsgId.rpcCall, { id, handler, func, args, notify: true });
                }
            }
            app.rpcService.onRpcCallRes(caller.id, {});
        } else if (RpcCaller.toSid === app.svr.id) {
            //本服直接调用
            let res = await app.rpcService.callRpcFunc(handler, func, args);
            app.rpcService.onRpcCallRes(caller.id, res);
        } else {
            let err = await app.rpcService.send(RpcCaller.toSid, RpcMsgId.rpcCall, { id, handler, func, args, notify: RpcCaller.notify });
            if (err) return app.rpcService.onRpcCallRes(caller.id, { err: { code: -1, msg: err } });
            else if (RpcCaller.notify) app.rpcService.onRpcCallRes(caller.id, {});
        }
    }

    /**
     * 调用结果返回
     * @param res 
     */
    onCallRes(res: RpcCallRes) {
        delete this.app.rpcService.rpcCallers[this.id];
        this._resolve(res);
        this.recy();
    }

    private onCallTimeout() {
        logger.error(`${this.toSid}.${this.hanlder}.${this.func}: rpc调用超时`, this, this.onCallTimeout);
        this.app.rpcService.onRpcCallRes(this.id, { err: { code: -2, msg: "rpc调用超时" } });
    }

    /**
     * 回收到对象池
     */
    recy() {
        clearTimeout(this._callTimeoutTimer);
        this._callTimeoutTimer = null;
        this._resolve = null;
        RpcCaller._pool.push(this);
    }

}